use crate::{
    comptime::{Lexer, Result},
    expr::{FuncProto, Literal, Operator},
    token::Delimiter,
    Expr, Token,
};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Not Implemented yet")]
    NotImplementedYet,
    #[error("Expect {expect}, found {found:?}")]
    UnexpectedToken { expect: &'static str, found: Token },
    #[error("Expect {after:?}, found {tokens}")]
    ExpectToken { after: Token, tokens: &'static str },
    #[error("Unclosed Group")]
    UnclosedGroup,
    #[error("Missing {0}")]
    MissingToken(&'static str),
    #[error("Missing {0}")]
    MissingExpr(&'static str),
}

pub struct Parser<'src> {
    pub(crate) lexer: Lexer<'src>,
}

impl<'src> Parser<'src> {
    pub fn new(src: &'src str) -> Self {
        Self {
            lexer: Lexer::new(src),
        }
    }

    pub fn parse(self) -> Result<Expr> {
        let mut exprs = self.parse_exprs()?;
        match exprs.len() {
            0 => Ok(Expr::Nil),
            1 => Ok(exprs.pop().unwrap()),
            _ => {
                exprs.reverse();
                Ok(Expr::Block(exprs))
            }
        }
    }

    pub fn parse_exprs(mut self) -> Result<Vec<Expr>> {
        let mut exprs = Vec::new();
        loop {
            match self.lexer.next_token()? {
                Token::EOF => return Ok(exprs),
                tok => exprs.push(self.parse_expr(tok)?),
            }
        }
    }

    pub fn parse_single_expr(&mut self) -> Result<Expr> {
        match self.lexer.next_token()? {
            Token::EOF => Err(Error::UnexpectedToken {
                expect: "any",
                found: Token::EOF,
            }
            .into()),
            token => self.parse_expr(token),
        }
    }

    pub fn parse_expr(&mut self, first: Token) -> Result<Expr> {
        match first {
            Token::Boolean(v) => Ok(Expr::Literal(Literal::from(v))),
            Token::Integer(v) => Ok(Expr::Literal(Literal::from(v))),
            Token::Real(v) => Ok(Expr::Literal(Literal::from(v))),
            Token::String(v) => Ok(Expr::Literal(Literal::from(v))),
            Token::Word(word) => self.parse_ident(word),
            Token::Begin(begin) => self.parse_group(begin),
            Token::Params(params) => self.parse_lambda(params),
            Token::SingleQuote => self.parse_symbol(),
            Token::End(_) | Token::Colon => Err(Error::UnexpectedToken {
                expect: "literals, words, (, [, {, | or '",
                found: first,
            }
            .into()),
            _ => panic!("Unreachable code! {first:?}"),
        }
    }

    fn parse_group(&mut self, begin: Delimiter) -> Result<Expr> {
        let mut exprs = vec![];

        loop {
            match self.lexer.next_token()? {
                Token::EOF => return Err(Error::UnclosedGroup.into()),
                Token::End(end) if end == begin => break,
                token => exprs.push(self.parse_expr(token)?),
            }
        }
        exprs.reverse();
        if let Delimiter::Bracket = begin {
            Ok(Expr::List(exprs))
        } else {
            if let Some(expr) = exprs.pop() {
                if let Expr::Ident(id) = &expr {
                    if id == "def" {
                        let id = exprs
                            .pop()
                            .and_then(Expr::into_ident)
                            .ok_or(Error::MissingToken("ident"))?;

                        let val = exprs
                            .pop()
                            .map(Box::new)
                            .ok_or(Error::MissingExpr("value"))?;

                        return Ok(Expr::Def(id, val));
                    } else if id == "begin" {
                        return Ok(Expr::Block(exprs));
                    } else if id == "let" {
                        return Ok(Expr::Let(
                            exprs
                                .pop()
                                .map(Box::new)
                                .ok_or(Error::MissingExpr("pairs"))?,
                            exprs
                                .pop()
                                .map(Box::new)
                                .ok_or(Error::MissingExpr("let body"))?,
                        ));
                    } else if let Ok(op) = Operator::try_from(id.as_str()) {
                        return Ok(Expr::Operation(op, exprs));
                    }
                }
                exprs.push(expr);
            } else {
                return Ok(Expr::Nil);
            }
            Ok(Expr::Compound(exprs))
        }
    }

    fn parse_lambda(&mut self, params: Vec<String>) -> Result<Expr> {
        let body = match self.lexer.next_token()? {
            Token::EOF => {
                return Err(Error::UnexpectedToken {
                    expect: "any without EOF",
                    found: Token::EOF,
                })?
            }
            token => self.parse_expr(token)?,
        };

        Ok(Expr::Proto(FuncProto {
            params,
            body: Box::new(body),
        }))
    }

    fn parse_symbol(&mut self) -> Result<Expr> {
        match self.lexer.next_token()? {
            Token::EOF => {
                Err(Error::UnexpectedToken {
                    expect: "any without '",
                    found: Token::EOF,
                })?
            }
            Token::SingleQuote => {
                Err(Error::UnexpectedToken {
                    expect: "any without '",
                    found: Token::SingleQuote,
                })?
            }
            t => Ok(Expr::Quoted(Box::new(self.parse_expr(t)?))),
        }
    }

    fn parse_ident(&self, word: String) -> Result<Expr> {
        Ok(Expr::Ident(word))
    }
}
